/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2019 Olivier Biot
 * http://www.melonjs.org
 */

(function () {

  // ensure that me.debug is defined
  me.debug = me.debug || {};

  /**
   * @classdesc
   * a simple debug panel plugin <br>
   * <img src="images/debugPanel.png"/> <br>
   * the debug panel provides the following information : <br>
   * &bull; amount of total objects currently active in the current stage <br>
   * &bull; amount of draws operation <br>
   * &bull; amount of body shape (for collision) <br>
   * &bull; amount of bounding box <br>
   * &bull; amount of sprites objects <br>
   * &bull; amount of objects currently inactive in the the object pool <br>
   * &bull; memory usage (Heap Memory information is only available under Chrome) <br>
   * &bull; frame update time (in ms) <br>
   * &bull; frame draw time (in ms) <br>
   * &bull; current fps rate vs target fps <br>
   * additionally, using the checkbox in the panel it is also possible to display : <br>
   * &bull; the hitbox or bounding box for all objects <br>
   * &bull; current velocity vector <br>
   * &bull; quadtree spatial visualization <br>
   * usage : <br>
   * &bull; upon loading the debug panel, it will be automatically registered under me.plugins.debugPanel <br>
   * &bull; you can then press the default "s" key to show or hide the panel, or use me.plugins.debugPanel.show() and me.plugins.debugPanel.show(), or add #debug as a parameter to your URL e.g. http://myURL/index.html#debug <br>
   * &bull; default key can be configured using the following parameters in the url : e.g. http://myURL/index.html#debugToggleKey=d
   * @class
   * @hideconstructor
   * @name DebugPanel
   * @see me.plugins
   * @public
   * @extends me.plugin.Base
   * @memberOf me
   * @constructor
   * @example
   * // load the debugPanel in your index.html file
   * <script type="text/javascript" src="plugins/debug/debugPanel.js"></script>
   */
  me.DebugPanel = me.plugin.Base.extend({
    /** @private */
    init: function (debugToggle) {
      // call the super constructor
      this._super(me.plugin.Base, "init");
      this.panel = new DebugPanel(debugToggle);

      // if "#debug" is present in the URL
      if (me.utils.getUriFragment().debug === true) {
        this.show();
      } // else keep it hidden
    },

    /**
     * show the debug panel
     * @public
     * @function
     * @memberOf me.DebugPanel
     */
    show: function () {
      this.panel.show();
    },

    /**
     * hide the debug panel
     * @public
     * @function
     * @memberOf me.DebugPanel
     */
    hide: function () {
      this.panel.hide();
    },

    /**
     * toggle the debug panel visibility state
     * @public
     * @function
     * @memberOf me.DebugPanel
     */
    toggle: function () {
      if (this.panel.visible) {
        this.panel.hide();
      } else {
        this.panel.show();
      }
    }


  });

  // PRIVATE components

  var DEBUG_HEIGHT = 50;

  var Counters = me.Object.extend({
    init: function (stats) {
      this.stats = {};
      this.reset(stats);
    },

    reset: function (stats) {
      var self = this;
      (stats || Object.keys(this.stats)).forEach(function (stat) {
        self.stats[stat] = 0;
      });
    },

    inc: function (stat, value) {
      this.stats[stat] += (value || 1);
    },

    get: function (stat) {
      return this.stats[stat];
    }
  });

  // embedded bitmap font data
  var fontDataSource =
    "info face=\"PressStart2P\" size=10 bold=0 italic=0 charset= unicode= stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=0,0 outline=0\n" +
    "common lineHeight=10 base=10 scaleW=128 scaleH=128 pages=1 packed=0\n" +
    "page id=0 file=\"PressStart2P.png\"\n" +
    "chars count=95\n" +
    "char id=32 x=1 y=1 width=0 height=0 xoffset=0 yoffset=10 xadvance=10 page=0 chnl=15\n" +
    "char id=33 x=1 y=2 width=5 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=34 x=1 y=13 width=8 height=5 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=35 x=7 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=36 x=1 y=19 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=37 x=1 y=30 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=38 x=1 y=41 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=39 x=10 y=12 width=4 height=5 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=40 x=1 y=52 width=6 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=41 x=1 y=63 width=6 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=42 x=1 y=74 width=10 height=8 xoffset=0 yoffset=1 xadvance=10 page=0 chnl=15\n" +
    "char id=43 x=8 y=52 width=9 height=8 xoffset=1 yoffset=1 xadvance=10 page=0 chnl=15\n" +
    "char id=44 x=8 y=61 width=5 height=5 xoffset=1 yoffset=6 xadvance=10 page=0 chnl=15\n" +
    "char id=45 x=8 y=67 width=9 height=2 xoffset=1 yoffset=4 xadvance=10 page=0 chnl=15\n" +
    "char id=46 x=14 y=61 width=4 height=4 xoffset=3 yoffset=6 xadvance=10 page=0 chnl=15\n" +
    "char id=47 x=12 y=18 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=48 x=18 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=49 x=12 y=29 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=50 x=12 y=40 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=51 x=22 y=29 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=52 x=23 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=53 x=29 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=54 x=1 y=83 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=55 x=1 y=94 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=56 x=1 y=105 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=57 x=1 y=116 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=58 x=18 y=51 width=4 height=8 xoffset=3 yoffset=1 xadvance=10 page=0 chnl=15\n" +
    "char id=59 x=12 y=70 width=5 height=9 xoffset=1 yoffset=1 xadvance=10 page=0 chnl=15\n" +
    "char id=60 x=12 y=80 width=8 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=61 x=23 y=23 width=10 height=5 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=62 x=18 y=66 width=8 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=63 x=23 y=40 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=64 x=33 y=29 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=65 x=23 y=51 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=66 x=34 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=67 x=40 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=68 x=12 y=91 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=69 x=21 y=77 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=70 x=27 y=62 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=71 x=34 y=40 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=72 x=34 y=51 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=73 x=44 y=23 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=74 x=45 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=75 x=51 y=1 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=76 x=12 y=102 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=77 x=12 y=113 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=78 x=22 y=102 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=79 x=23 y=88 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=80 x=32 y=73 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=81 x=38 y=62 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=82 x=23 y=113 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=83 x=33 y=99 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=84 x=34 y=84 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=85 x=43 y=73 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=86 x=34 y=110 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=87 x=44 y=84 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=88 x=44 y=95 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=89 x=45 y=106 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=90 x=45 y=117 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=91 x=45 y=34 width=6 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=92 x=45 y=45 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=93 x=52 y=34 width=6 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=94 x=34 y=23 width=8 height=4 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=95 x=34 y=121 width=10 height=2 xoffset=0 yoffset=9 xadvance=10 page=0 chnl=15\n" +
    "char id=96 x=15 y=12 width=4 height=4 xoffset=4 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=97 x=54 y=23 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=98 x=56 y=12 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=99 x=62 y=1 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=100 x=49 y=56 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=101 x=56 y=45 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=102 x=59 y=31 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=103 x=54 y=67 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=104 x=60 y=54 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=105 x=67 y=42 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=106 x=67 y=9 width=8 height=11 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=107 x=69 y=21 width=10 height=10 xoffset=0 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=108 x=76 y=1 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=109 x=76 y=12 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=110 x=69 y=32 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=111 x=86 y=1 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=112 x=97 y=1 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=113 x=108 y=1 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=114 x=87 y=10 width=9 height=8 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=115 x=97 y=11 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=116 x=108 y=11 width=9 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=117 x=87 y=19 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=118 x=98 y=19 width=9 height=8 xoffset=1 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=119 x=80 y=27 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=120 x=108 y=22 width=10 height=8 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=121 x=91 y=28 width=10 height=9 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=122 x=80 y=35 width=10 height=7 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=123 x=118 y=11 width=6 height=10 xoffset=3 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=124 x=102 y=28 width=4 height=10 xoffset=4 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=125 x=119 y=22 width=6 height=10 xoffset=1 yoffset=0 xadvance=10 page=0 chnl=15\n" +
    "char id=126 x=91 y=38 width=10 height=5 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15\n" +
    "char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=3 xadvance=10 page=0 chnl=15";

  var fontImageSource = "";

  var DebugPanel = me.Renderable.extend({
    /** @private */
    init: function (debugToggle) {
      // call the super constructor
      this._super(me.Renderable, "init", [0, 0, me.video.renderer.getWidth(), DEBUG_HEIGHT]);

      // enable collision and event detection
      this.isKinematic = false;

      // minimum melonJS version expected
      this.version = "8.0.0";

      // to hold the debug options
      // clickable rect area
      this.area = {};

      // Useful counters
      this.counters = new Counters([
        "shapes",
        "sprites",
        "velocity",
        "bounds",
        "children"
      ]);

      // for z ordering
      // make it ridiculously high
      this.pos.z = Infinity;

      // visibility flag
      this.visible = false;

      // frame update time in ms
      this.frameUpdateTime = 0;

      // frame draw time in ms
      this.frameDrawTime = 0;

      // set the object GUID value
      this.GUID = "debug-" + me.utils.createGUID();

      // set the object entity name
      this.name = "me.debugPanel";

      // persistent
      this.isPersistent = true;

      // a floating object
      this.floating = true;

      // renderable
      this.isRenderable = true;

      // always update, even when not visible
      this.alwaysUpdate = true;

      // WebGL/Canvas compatibility
      this.canvas = me.video.createCanvas(this.width, this.height, true);

      // create a default font, with fixed char width
      this.font_size = 10;
      this.mod = 2;
      if (this.width < 500) {
        this.font_size = 7;
        this.mod = this.mod * (this.font_size / 10);
      }

      // create the bitmapfont
      var fontImage = new Image();
      fontImage.src = fontImageSource;

      this.font = new me.BitmapText(0, 0, {
        fontData: fontDataSource,
        font: fontImage
      });
      this.font.name = "debugPanelFont";


      // free static ressources
      fontImageSource = null;
      fontDataSource = null;

      // clickable areas
      var size = 10 * this.mod;
      this.area.renderHitBox = new me.Rect(250, 2, size, size);
      this.area.renderVelocity = new me.Rect(250, 17, size, size);
      this.area.renderQuadTree = new me.Rect(410, 2, size, size);

      // enable the FPS counter
      me.debug.displayFPS = true;

      var self = this;

      // add some keyboard shortcuts
      this.debugToggle = debugToggle || me.input.KEY.S;
      this.keyHandler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode) {
        if (keyCode === self.debugToggle) {
          me.plugins.debugPanel.toggle();
        }
      });

      // some internal string/length
      this.help_str = "[" + String.fromCharCode(32 + this.debugToggle) + "]show/hide";
      this.help_str_len = this.font.measureText(this.help_str).width;
      this.fps_str_len = this.font.measureText("00/00 fps").width;
      this.memoryPositionX = 325 * this.mod;

      // resize the panel if the browser is resized
      me.event.subscribe(me.event.CANVAS_ONRESIZE, function (w) {
        self.resize(w, DEBUG_HEIGHT);
      });

      //patch patch patch !
      this.patchSystemFn();

      this.anchorPoint.set(0, 0);
    },

    /**
     * patch system fn to draw debug information
     * @ignore
     */
    patchSystemFn: function () {
      var hash = me.utils.getUriFragment();

      // add a few new debug flag (if not yet defined)
      me.debug.renderHitBox = me.debug.renderHitBox || hash.hitbox || false;
      me.debug.renderVelocity = me.debug.renderVelocity || hash.velocity || false;
      me.debug.renderQuadTree = me.debug.renderQuadTree || hash.quadtree || false;

      var _this = this;
      var bounds = new me.Rect(0, 0, 0, 0);

      // patch me.game.update
      me.plugin.patch(me.game, "update", function (dt) {
        var frameUpdateStartTime = window.performance.now();

        this._patched.apply(this, arguments);

        // calculate the update time
        _this.frameUpdateTime = window.performance.now() - frameUpdateStartTime;
      });

      // patch me.game.draw
      me.plugin.patch(me.game, "draw", function () {
        var frameDrawStartTime = window.performance.now();

        _this.counters.reset();

        this._patched.apply(this, arguments);

        // calculate the drawing time
        _this.frameDrawTime = window.performance.now() - frameDrawStartTime;
      });

      // patch sprite.js
      me.plugin.patch(me.Sprite, "draw", function (renderer) {

        // call the original me.Sprite.draw function
        this._patched(renderer);

        // don't do anything else if the panel is hidden
        if (_this.visible) {

          // increment the sprites counter
          _this.counters.inc("sprites");

          // draw the sprite rectangle
          if (me.debug.renderHitBox) {
            var bounds = this.getBounds();
            var ax = this.anchorPoint.x * bounds.width,
              ay = this.anchorPoint.y * bounds.height;

            var ancestor = this.ancestor;
            if (ancestor instanceof me.Container && ancestor.root === false) {
              ax -= ancestor._absPos.x;
              ay -= ancestor._absPos.y;
            } else if (ancestor instanceof me.Entity) {
              ancestor = ancestor.ancestor;
              if (ancestor instanceof me.Container && ancestor.root === false) {
                // is this correct ???
                ax = ay = 0;
              }
            }

            // translate back as the bounds position
            // is already adjusted to the anchor Point
            renderer.translate(ax, ay);

            renderer.setColor("green");
            renderer.stroke(bounds);

            renderer.translate(-ax, -ay);

            // the sprite mask if defined
            if (typeof this.mask !== "undefined") {
              renderer.setColor("orange");
              renderer.stroke(this.mask);
            }

            if (typeof this.body !== "undefined") {
              if (!this.currentTransform.isIdentity()) {
                // if any transform were applied to me.Sprite
                // we need to reset the context so that we
                // can draw the body shapes properly
                renderer.save();
                renderer.resetTransform();
                renderer.translate(-ax, -ay);
              }
              renderer.translate(this.pos.x, this.pos.y);
              // draw all defined shapes
              renderer.setColor("red");
              for (var i = this.body.shapes.length, shape; i--, (shape = this.body.shapes[i]);) {
                renderer.stroke(shape);
                _this.counters.inc("shapes");
              }
              if (!this.currentTransform.isIdentity()) {
                renderer.restore();
              }
            }
          }
        }
      });


      me.plugin.patch(me.BitmapText, "draw", function (renderer) {
        // call the original me.Sprite.draw function
        this._patched.apply(this, arguments);

        // draw the font rectangle
        if (_this.visible && me.debug.renderHitBox && this.name !== "debugPanelFont") {
          var bounds = this.getBounds();

          if (typeof this.ancestor !== "undefined") {
            var ax = this.anchorPoint.x * bounds.width,
              ay = this.anchorPoint.y * bounds.height;
            // translate back as the bounds position
            // is already adjusted to the anchor Point
            renderer.translate(ax, ay);
          } else {
            renderer.save();
          }

          renderer.setColor("orange");
          renderer.stroke(bounds);
          _this.counters.inc("bounds");

          if (typeof this.ancestor === "undefined") {
            renderer.restore();
          }
        }
      });

      // patch font.js
      me.plugin.patch(me.Text, "draw", function (renderer, text, x, y) {
        // call the original me.Text.draw function
        this._patched.apply(this, arguments);

        // call the original me.Sprite.draw function
        if (_this.visible && me.debug.renderHitBox) {
          if (typeof this.ancestor === "undefined") {
            renderer.save();
          }
          renderer.setColor("orange");
          renderer.stroke(this.getBounds());
          _this.counters.inc("bounds");
          if (typeof this.ancestor === "undefined") {
            renderer.restore();
          }
        }
      });

      // patch font.js
      me.plugin.patch(me.Text, "drawStroke", function (renderer, text, x, y) {
        // call the original me.Font.drawStroke function
        this._patched.apply(this, arguments);

        // draw the font rectangle
        if (_this.visible && me.debug.renderHitBox) {
          if (typeof this.ancestor === "undefined") {
            renderer.save();
          }
          renderer.setColor("orange");
          renderer.stroke(this.getBounds());
          _this.counters.inc("bounds");
          if (typeof this.ancestor === "undefined") {
            renderer.restore();
          }
        }
      });

      // patch entities.js
      me.plugin.patch(me.Entity, "postDraw", function (renderer) {
        // don't do anything else if the panel is hidden
        if (_this.visible) {
          // increment the bounds counter
          _this.counters.inc("bounds");

          // check if debug mode is enabled
          if (me.debug.renderHitBox) {
            renderer.save();

            renderer.translate(
              -this.pos.x - this.body.pos.x - this.ancestor._absPos.x,
              -this.pos.y - this.body.pos.y - this.ancestor._absPos.y
            );

            if (this.renderable instanceof me.Renderable) {
              renderer.translate(
                -this.anchorPoint.x * this.body.width,
                -this.anchorPoint.y * this.body.height
              );
            }

            // draw the bounding rect shape
            renderer.setColor("orange");
            renderer.stroke(this.getBounds());

            renderer.translate(
              this.pos.x + this.ancestor._absPos.x,
              this.pos.y + this.ancestor._absPos.y
            );

            // draw all defined shapes
            renderer.setColor("red");
            for (var i = this.body.shapes.length, shape; i--, (shape = this.body.shapes[i]);) {
              renderer.stroke(shape);
              _this.counters.inc("shapes");
            }
            renderer.restore();
          }

          if (me.debug.renderVelocity && (this.body.vel.x || this.body.vel.y)) {
            bounds.copy(this.getBounds());
            bounds.pos.sub(this.ancestor._absPos);
            // draw entity current velocity
            var x = bounds.width / 2;
            var y = bounds.height / 2;

            renderer.save();
            renderer.setLineWidth(1);

            renderer.setColor("blue");
            renderer.translate(-x, -y);
            renderer.strokeLine(0, 0, ~~(this.body.vel.x * (bounds.width / 2)), ~~(this.body.vel.y * (bounds.height / 2)));
            _this.counters.inc("velocity");

            renderer.restore();
          }
        }
        // call the original me.Entity.postDraw function
        this._patched.apply(this, arguments);
      });

      // patch container.js
      me.plugin.patch(me.Container, "draw", function (renderer, rect) {
        // call the original me.Container.draw function
        this._patched.apply(this, arguments);

        // check if debug mode is enabled
        if (!_this.visible) {
          // don't do anything else if the panel is hidden
          return;
        }

        // increment counters
        _this.counters.inc("bounds");
        _this.counters.inc("children");

        if (me.debug.renderHitBox) {
          renderer.save();
          renderer.setLineWidth(1);

          if (!this.root) {
            renderer.translate(
              -this._absPos.x,
              -this._absPos.y
            );
          }

          // draw the bounding rect shape
          renderer.setColor("orange");
          renderer.stroke(this.getBounds());

          // draw the children bounding rect shape
          renderer.setColor("purple");
          renderer.stroke(this.childBounds);

          renderer.restore();
        }
      });
    },

    /**
     * show the debug panel
     * @ignore
     */
    show: function () {
      if (!this.visible) {
        // add the debug panel to the game world
        me.game.world.addChild(this, Infinity);
        // register a mouse event for the checkboxes
        me.input.registerPointerEvent("pointerdown", this, this.onClick.bind(this));
        // mark it as visible
        this.visible = true;
        // force repaint
        me.game.repaint();
      }
    },

    /**
     * hide the debug panel
     * @ignore
     */
    hide: function () {
      if (this.visible) {
        // release the mouse event for the checkboxes
        me.input.releasePointerEvent("pointerdown", this);
        // remove the debug panel from the game world
        me.game.world.removeChild(this, true);
        // mark it as invisible
        this.visible = false;
        // force repaint
        me.game.repaint();
      }
    },


    /** @private */
    update: function () {
      // update the FPS counter
      me.timer.countFPS();

      return this.visible;
    },

    /** @private */
    onClick: function (e) {
      // check the clickable areas
      if (this.area.renderHitBox.containsPoint(e.gameX, e.gameY)) {
        me.debug.renderHitBox = !me.debug.renderHitBox;
      } else if (this.area.renderVelocity.containsPoint(e.gameX, e.gameY)) {
        // does nothing for now, since velocity is
        // rendered together with hitboxes (is a global debug flag required?)
        me.debug.renderVelocity = !me.debug.renderVelocity;
      } else if (this.area.renderQuadTree.containsPoint(e.gameX, e.gameY)) {
        me.debug.renderQuadTree = !me.debug.renderQuadTree;
      }
      // force repaint
      me.game.repaint();
    },

    /** @private */
    drawQuadTreeNode: function (renderer, node) {
      var bounds = node.bounds;

      // draw the current bounds
      if (node.nodes.length === 0) {
        // cap the alpha value to 0.4 maximum
        var _alpha = (node.objects.length * 0.4) / me.collision.maxChildren;
        if (_alpha > 0.0) {
          renderer.save();
          renderer.setColor("rgba(255,0,0," + _alpha + ")");
          renderer.fillRect(bounds.pos.x, bounds.pos.y, bounds.width, bounds.height);
          renderer.restore();
        }
      } else {
        //has subnodes? drawQuadtree them!
        for (var i = 0; i < node.nodes.length; i++) {
          this.drawQuadTreeNode(renderer, node.nodes[i]);
        }
      }
    },

    /** @private */
    drawQuadTree: function (renderer) {
      var x = me.game.viewport.pos.x;
      var y = me.game.viewport.pos.y;

      renderer.translate(-x, -y);

      this.drawQuadTreeNode(renderer, me.game.world.broadphase);

      renderer.translate(x, y);
    },

    /** @private */
    drawMemoryGraph: function (renderer, endX) {
      if (window.performance && window.performance.memory) {
        var usedHeap = me.Math.round(window.performance.memory.usedJSHeapSize / 1048576, 2);
        var totalHeap = me.Math.round(window.performance.memory.totalJSHeapSize / 1048576, 2);
        var maxLen = ~~(endX - this.memoryPositionX - 5);
        var len = maxLen * (usedHeap / totalHeap);

        renderer.setColor("#0065AD");
        renderer.fillRect(this.memoryPositionX, 0, maxLen, 20);
        renderer.setColor("#3AA4F0");
        renderer.fillRect(this.memoryPositionX + 1, 1, len - 1, 17);

        this.font.draw(renderer, "Heap : " + usedHeap + "/" + totalHeap + " MB", this.memoryPositionX + 5, 2 * this.mod);
      } else {
        // Heap Memory information not available
        this.font.draw(renderer, "Heap : ??/?? MB", this.memoryPositionX, 2 * this.mod);
      }
      this.font.draw(renderer, "Pool : " + me.pool.getInstanceCount(), this.memoryPositionX, 10 * this.mod);
    },

    /** @private */
    draw: function (renderer) {
      renderer.save();

      // draw the QuadTree (before the panel)
      if (me.debug.renderQuadTree === true) {
        this.drawQuadTree(renderer);
      }

      // draw the panel
      renderer.setGlobalAlpha(0.5);
      renderer.setColor("black");
      renderer.fillRect(
        this.left, this.top,
        this.width, this.height
      );
      renderer.setGlobalAlpha(1.0);
      renderer.setColor("white");

      this.font.textAlign = "left";

      this.font.draw(renderer, "#objects : " + me.game.world.children.length, 5 * this.mod, 2 * this.mod);
      this.font.draw(renderer, "#draws   : " + me.game.world.drawCount, 5 * this.mod, 10 * this.mod);

      // debug checkboxes
      this.font.draw(renderer, "?hitbox   [" + (me.debug.renderHitBox ? "x" : " ") + "]", 75 * this.mod, 2 * this.mod);
      this.font.draw(renderer, "?velocity [" + (me.debug.renderVelocity ? "x" : " ") + "]", 75 * this.mod, 10 * this.mod);

      this.font.draw(renderer, "?QuadTree [" + (me.debug.renderQuadTree ? "x" : " ") + "]", 150 * this.mod, 2 * this.mod);

      // draw the update duration
      this.font.draw(renderer, "Update : " + this.frameUpdateTime.toFixed(2) + " ms", 225 * this.mod, 2 * this.mod);
      // draw the draw duration
      this.font.draw(renderer, "Draw   : " + this.frameDrawTime.toFixed(2) + " ms", 225 * this.mod, 10 * this.mod);


      // Draw color code hints (not supported with bitmapfont)
      //this.font.fillStyle.copy("red");
      this.font.draw(renderer, "Shapes   : " + this.counters.get("shapes"), 5 * this.mod, 17 * this.mod);

      //this.font.fillStyle.copy("green");
      this.font.draw(renderer, "Sprites   : " + this.counters.get("sprites"), 75 * this.mod, 17 * this.mod);

      //this.font.fillStyle.copy("blue");
      this.font.draw(renderer, "Velocity  : " + this.counters.get("velocity"), 150 * this.mod, 17 * this.mod);

      //this.font.fillStyle.copy("orange");
      this.font.draw(renderer, "Bounds : " + this.counters.get("bounds"), 225 * this.mod, 17 * this.mod);

      //this.font.fillStyle.copy("purple");
      this.font.draw(renderer, "Children : " + this.counters.get("children"), 325 * this.mod, 17 * this.mod);

      // Reset font style
      //this.font.setFont("courier", this.font_size, "white");

      // draw the memory heap usage
      var endX = this.width - 5;
      this.drawMemoryGraph(renderer, endX - this.help_str_len);

      this.font.textAlign = "right";

      // some help string
      this.font.draw(renderer, this.help_str, endX, 17 * this.mod);

      //fps counter
      var fps_str = me.timer.fps + "/" + me.timer.maxfps + " fps";
      this.font.draw(renderer, fps_str, endX, 2 * this.mod);

      renderer.restore();
    },

    /** @private */
    onDestroyEvent: function () {
      // hide the panel
      this.hide();
      // unbind keys event
      me.input.unbindKey(this.toggleKey);
      me.event.unsubscribe(this.keyHandler);
    }
  });

  // automatically register the debug panel
  me.event.subscribe(me.event.VIDEO_INIT, function () {
    var toggleKey = me.utils.getUriFragment().debugToggleKey;
    me.utils.function.defer(me.plugin.register, this, me.DebugPanel, "debugPanel",
      toggleKey ? toggleKey.charCodeAt(0) - 32 : undefined
    );
  });

  /*---------------------------------------------------------*/
  // END END END
  /*---------------------------------------------------------*/
})();
